dojo.provide("dojoscratch.Scalable");
dojo.require("dojox.gfx.matrix");
dojo.require("dojoscratch.Scaler");

//based on http://archive.dojotoolkit.org/nightly/checkout/dojox/gfx/Moveable.js
dojo.declare("dojoscratch.Scalable", null, {
	constructor: function(shape, params) {
		this._shape = shape;
		this._delay = (params && params.delay > 0) ? params.delay : 0;
		this._scaler = (params && params.scaler) ? params.scaler : dojoscratch.Scaler;

		this._events = [
		   			this._shape.connect("onmousedown", this, "_onMouseDown")
		   			// cancel text selection and text dragging
		   			//, dojo.connect(this.handle, "ondragstart",   dojo, "stopEvent")
		   			//, dojo.connect(this.handle, "onselectstart", dojo, "stopEvent")
		   		];

		this._addScalePoint();
		this._initTransform();
	},
	_initTransform: function() {
		var realSize = this._realBoundingBox();
		var originalSize = this._getBoundingBox();
		
		//get previous scale:
		this._dx = realSize.width - originalSize.width;
		this._dy = realSize.height - originalSize.height;

		this._height = originalSize.height;
		this._width = originalSize.width;
		
		//translation matrixes
		var realPosition = this._realBoundingBox();
		this._translateTopLeftMatrix = dojox.gfx.matrix.translate(-(realPosition.x), -(realPosition.y));
		this._positionRestoreMatrix = dojox.gfx.matrix.translate((realPosition.x), (realPosition.y));

		//remove scale factor from matrix:
		var inverseMatrix = dojox.gfx.matrix.multiply(this._positionRestoreMatrix, dojox.gfx.matrix.invert(this._scaleMatrix()), this._translateTopLeftMatrix);
		this._noScaleMatrix = dojox.gfx.matrix.multiply(inverseMatrix, this._getFirstShape()._getRealMatrix());
	},
	_scaleMatrix: function() {
		return dojox.gfx.matrix.scale(this._width ==0?1:(this._width+this._dx)/this._width,
									  this._height==0?1:(this._height+this._dy)/this._height);
		
	},
	_addScalePoint: function() {
		//shape can be moved somewhere, get real coordinates
		
		var box = this._getBoundingBox();
		var position = dojox.gfx.matrix.multiplyPoint(this._getFirstShape().getTransform(), box.x, box.y);

		this._marker= this._shape.createCircle({
			"type" : "circle",
			"cx" : 	position.x+box.width,
			"cy" : position.y+box.height,
			"r" : 3
		})
        .setStroke({
        	//one from: http://docs.dojocampus.org/dojox/gfx-visual-properties
				style : "DashDot"
		})
		.setFill("white");

	},
	// methods
	destroy: function(){
		this._marker.removeShape(true);
		// summary: stops watching for possible scale, deletes all references, so the object can be garbage-collected
		dojo.forEach(this._events, this._shape.disconnect, this._shape);
		this._events = null;
		this._shape = null;
	},

	// mouse event processors
	_onMouseDown: function(e){
		// summary: event processor for onmousedown, creates a Scaler for the shape
		// e: Event: mouse event
		if(this._delay){
			this._events.push(this._shape.connect("onmousemove", this, "_onMouseMove"));
			this._events.push(this._shape.connect("onmouseup", this, "_onMouseUp"));
			this._lastX = e.clientX;
			this._lastY = e.clientY;
		}else{
			new this._scaler(this._shape, e, this);
		}
		
		dojo.stopEvent(e);
	},
	_onMouseMove: function(e){
		// summary: event processor for onmousemove, used only for delayed drags
		// e: Event: mouse event
		if(Math.abs(e.clientX - this._lastX) > this._delay || Math.abs(e.clientY - this._lastY) > this._delay){
			this._onMouseUp(e);
			new this._scaler(this._shape, e, this);
		}
		dojo.stopEvent(e);
	},
	_onMouseUp: function(e){
		// summary: event processor for onmouseup, used only for delayed delayed drags
		// e: Event: mouse event
		this._shape.disconnect(this._events.pop());
		this._shape.disconnect(this._events.pop());
	},
	// local events
	onScaleStart: function(/* dojoscratch.Scaler */ scaler){
		// summary: called before every scale operation
		dojo.publish("/gfx/scale/start", [scaler]);
		dojo.addClass(dojo.body(), "dojoScale");
	},
	onScaleStop: function(/* dojoscratch.Scaler */ scaler){
		// summary: called after every scale operation
		dojo.publish("/gfx/scale/stop", [scaler]);
		dojo.removeClass(dojo.body(), "dojoScale");
	},
	onFirstScale: function(/* dojoscratch.Scaler */ scaler){
		// summary: called during the very first scale notification,
		//	can be used to initialize coordinates, can be overwritten.

		// default implementation does nothing
	},
	onScale: function(/* dojoscratch.Scaler */ scaler, /* Object */ shift){
		// summary: called during every scale notification,
		//	should actually scale the node, can be overwritten.
		this.onScaling(scaler, shift);
		
		//add this scale to sum
		this._dx += shift.dx;
		this._dy += shift.dy;
		
		//matrix operations can be found at: http://api.dojotoolkit.org/jsdoc/1.3/dojox.gfx.matrix
		var transfromMatrix = dojox.gfx.matrix.multiply( this._positionRestoreMatrix, this._scaleMatrix(), this._translateTopLeftMatrix, this._noScaleMatrix);
		
		this._shape.setTransform(transfromMatrix);
//		this._shape.applyLeftTransform(transfromMatrix);
		this.onScaled(scaler, shift);
	},
	onScaling: function(/* dojoscratch.Scaler */ scaler, /* Object */ shift){
		// summary: called before every incremental scale,
		//	can be overwritten.

		// default implementation does nothing
	},
	onScaled: function(/* dojoscratch.Scaler */ scaler, /* Object */ shift){
		// summary: called after every incremental scale,
		//	can be overwritten.

		// default implementation does nothing
	},
	_getBoundingBox :function() {
		return this._getFirstShape().getBoundingBox();
	},
	_getFirstShape :function() {
		var result = this._shape;
		if(this._shape instanceof dojox.gfx.Group) {
			result = this._shape.children[0];
		}
		return result;
	},
	_realBoundingBox: function() {
		//XXX what about shape.getTransformedBoundingBox() ?
		var box = this._getBoundingBox();
		var realMatrix = this._getFirstShape()._getRealMatrix();

		//from http://www.dojotoolkit.org/forum/dojox-dojox/dojox-support/how-use-dojox-gfx-moveable
		var position = dojox.gfx.matrix.multiplyPoint(realMatrix, box.x, box.y);

		//beacause trasformation has translation to top-left
		var size = dojox.gfx.matrix.multiplyPoint(this._shape.getTransform(), box.width, box.height);
		var sizeOffset = dojox.gfx.matrix.multiplyPoint(this._shape.getTransform(), 0, 0);
		return {x: position.x, y: position.y, width: size.x-sizeOffset.x, height: size.y-sizeOffset.y};
	}
});
